/**
 * map_feature.
 * 系统内建核心组件, 为系统提供最基础的几种Map Feature.
 */

var FG;
if (!FG)
    FG = {};

FG.FeatureTagMixin = {
    setTag: function(tag) {
        this.tag = tag;
    },

    getTag: function() {
        return this.tag;
    }
};

FG.PointMarker = L.Marker.extend({
    statics: {
        className: 'PointMarker'
    },

    includes: [FG.FeatureTagMixin],
    
    className: 'PointMarker',
    parentClass: L.Marker,
    
    initialize: function(latlng, options) {
        this.parentClass.prototype.initialize.call(this, latlng, options);
        if (_.has(options, 'tag')) {
            this.setTag(options.tag)
        }
    },
    
    updateFeature: function(latlngs, featureOpts) {
        this.setLatLng(latlngs);
        if (_.has(featureOpts, 'tag')) {
            this.setTag(featureOpts.tag)
        }
        if (!!featureOpts.icon) this.setIcon(featureOpts.icon);
        if (!!featureOpts.opacity || featureOpts === 0) this.setOpacity(featureOpts.opacity);
        if (!!featureOpts.zIndexOffset || featureOpts.zIndexOffset === 0) this.setZIndexOffset(featureOpts.zIndexOffset);
    },
    
    updateFromModel: function(model) {
        // var info = model.get('info');
        // this.setLatLng([info.lat, info.lon]);
    },

    bringToFront: function() {},
    bringToBack: function() {}
});

FG.RegionMixin = {
    onAdd: function(map) {
        this.parentClass.prototype.onAdd.call(this, map);

        if (!this._image) {
            this._initImage();
            //this._initEvents();
        }
        map._panes.overlayPane.appendChild(this._image);
        map.on('viewreset', this._reset, this);
        if (map.options.zoomAnimation && L.Browser.any3d) {
            map.on('zoomanim', this._animateZoom, this);
        }
        this._reset();
    },

    onRemove: function(map) {
        this.parentClass.prototype.onRemove.call(this, map);
        map.getPanes().overlayPane.removeChild(this._image);
        map.off('viewreset', this._reset, this);
        if (map.options.zoomAnimation && L.Browser.any3d) {
            map.off('zoomanim', this._animateZoom, this);
        }
    },

    _initImage: function () {
        this._image = L.DomUtil.create('div', 'leaflet-image-layer leaflet-clickable rect_figure_layer nodata');
        this.$image = $(this._image);
        this.$imageImg = $('<img />').appendTo(this.$image);
        this.imageUrl = '';

        if (this._map.options.zoomAnimation && L.Browser.any3d) {
            L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
        } else {
            L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
        }

        this._updateImageOpacity();
    },

    setImageOpacity: function(opacity) {
        this.options.imageOpacity = opacity;
        this._updateImageOpacity();
        return this;
    },


    _updateImageOpacity: function () {
        if (!!this._image) {
            L.DomUtil.setOpacity(this._image, this.options.imageOpacity);
        }
    },

    _animateZoom: function (e) {
        var map = this._map,
            image = this._image,
            scale = map.getZoomScale(e.zoom),
            nw = this._bounds.getNorthWest(),
            se = this._bounds.getSouthEast(),

            topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
            size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
            origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));

        image.style[L.DomUtil.TRANSFORM] =
            L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
    },

    _reset: function () {
        var image = this._image,
            topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
            size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);

        L.DomUtil.setPosition(image, topLeft);

        image.style.width = size.x + 'px';
        image.style.height = size.y + 'px';

        //this.$image.css('width', size.x + 'px');
        //this.$image.css('height', size.y + 'px');

        if (!!this.chart) {
            this.chart.setSize(size.x, size.y);
        }
    },

    loadImage: function(imageUrl) {
        var this_ = this;
        this.dropImage();
        this.imageUrl = imageUrl;
        if (!!this.$imageImg) {
            this.$imageImg.attr('src', '/data/'+imageUrl).on('load', function() {
                //this_.$image.removeClass('nodata');
                this_.setStyle({fillOpacity: 0});
                this_.$imageImg.show();
            });
        }
    },

    dropImage: function() {
        this.imageUrl = '';
        if (!! this.$imageImg ) {
            this.$imageImg.attr('src', '');
            this.$imageImg.hide();
        }
        //if (!! this.$image ) {
        //    //this.$image.addClass('nodata');
        //}
        this.setStyle({fillOpacity: 0.2});
    },

    // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
    bringToFront: function () {
        if (!this._map)
            return;

        if (this._image) {
            this._map._panes.overlayPane.appendChild(this._image);
        }
        this.setStyle({opacity: 0.9});
        return this;
    },

    bringToBack: function () {
        if (!this._map)
            return;

        var pane = this._map._panes.overlayPane;
        if (this._image) {
            pane.insertBefore(this._image, pane.firstChild);
        }
        this.setStyle({opacity: 0.3});
        return this;
    },

    plotData: function (databundle, opts) {
        var this_ = this;
        opts = _.extend({vrange: 'fix'}, opts);
        databundle.getData().done(function(itemlist) {
            var data = [];
            var origData = itemlist[0].data;
            var suggest_range = origData.data.suggest_range;
            var cmapName = FG.getProperCmap(origData.data.suggest_cmap);
            var units = origData.data.units;
            var vrangeType = opts.vrange;
            var fit_range = origData.data.fit_range,
                fix_range = origData.data.fix_range,
                vranged = {fit: fit_range, fix: fix_range},
                vmin = vranged[vrangeType][0],
                vmax = vranged[vrangeType][1];

            var figUrlTemplate = _.template(origData.data.url_para);
            var figUrl = opts.uri + '?' + figUrlTemplate({
                    format: 'figure',
                    pitem: opts.widget.model.get('uuid'),
                    lat1: this_._bounds.getSouth(),
                    lat2: this_._bounds.getNorth(),
                    lon1: this_._bounds.getWest(),
                    lon2: this_._bounds.getEast(),
                    projection: 'merc',
                    vmin: vmin,
                    vmax: vmax,
                    cmap: cmapName
                });
            DX.warn(figUrl);
            this_.loadImage(figUrl);
            //this_.$image.html('<img src="/data/'+figUrl+'" style="width:100%; height: 100%" />')
            if (!!opts) {
                if (!!opts.callback) {
                    opts.callback(itemlist);
                }
                if (!!opts.widget) {
                    opts.widget.setColorbar({
                        cmap: cmapName,
                        vmin: vmin,
                        vmax: vmax,
                        units: units
                    })
                }
            }
        });
        //this.$figure.html(DX.gen_uuid8());
    }
};

FG.RectFeature2 = L.Rectangle.extend({
    statics: {
        className: 'RectFeature'
    },
    
    className: 'RectFeature',
    parentClass: L.Rectangle,

    options: {
        color: '#fff',
        opacity: 0.9,
        weight: 2,
        fillColor: '#fff',
        fillOpacity: 0.2,
        imageOpacity: 0
    },

    includes: [FG.RegionMixin, FG.FeatureTagMixin],

    initialize: function(latLngBounds, options) {
        this.parentClass.prototype.initialize.call(this, latLngBounds, options);
        this._bounds = L.latLngBounds(latLngBounds);
        this._latlng = this._bounds.getNorthEast();
        if (_.has(options, 'tag')) {
            this.setTag(options.tag)
        }
    },

    setBounds: function(bounds) {
        this._bounds = L.latLngBounds(bounds);
        this._latlng = this._bounds.getNorthEast();
        this._reset();
        this.parentClass.prototype.setBounds.call(this, bounds);
    },
    
    updateFeature: function(latlngs, featureOpts) {
        if (_.has(featureOpts, 'tag')) {
            this.setTag(featureOpts.tag)
        }
        this.setBounds(latlngs);
        this.setStyle(featureOpts);
    },
    
    updateFromModel: function (model) {
        var info = model.get('info');
        this.setBounds([
            [info.lat1, info.lon1],
            [info.lat2, info.lon2]
        ]);
    }
});

FG.MultiPolygonFeature = L.MultiPolygon.extend({
    statics: {
        className: 'PolygonFeature'
    },
    
    className: 'PolygonFeature',
    parentClass: L.MultiPolygon,

    options: {
        color: '#fff',
        opacity: 0.9,
        weight: 2,
        fillColor: '#fff',
        fillOpacity: 0.2,
        imageOpacity: 0
    },

    includes: [FG.RegionMixin, FG.FeatureTagMixin],

    initialize: function(latlngs, options) {
        this.parentClass.prototype.initialize.call(this, latlngs, options);
        this._bounds = this.getBounds();
        this._latlng = this._bounds.getNorthEast();
        if (_.has(options, 'tag')) {
            this.setTag(options.tag)
        }
    },

    updateFeature: function(latlngs, featureOpts) {
        if (_.has(featureOpts, 'tag')) {
            this.setTag(featureOpts.tag)
        }
        this.setLatLngs(latlngs);
        this._bounds = this.getBounds();
        this._latlng = this._bounds.getNorthEast();
        this.setStyle(featureOpts);
    },
    
    updateFromModel: function(model) {
        if (!model.mapData) {
            return;
        }
        var geometry = model.mapData.get('geometry');

        var latlngs = [];
        _.each(geometry.coordinates, function(mpoly) {
            _.each(mpoly, function(lonlats) {
                var onePoly = _.map(lonlats, function(lonlat) {
                    return new L.LatLng(lonlat[1], lonlat[0])
                });
                onePoly.pop();
                latlngs.push(onePoly);
            })
        });
        this.setLatLngs(latlngs);
        this._bounds = this.getBounds();
        this._latlng = this._bounds.getNorthEast();
    },

    closePopup: function () {
        // close popup on the first layer
        for (var id in this._layers) {
            this._layers[id].closePopup();
            break;
        }
        return this;
    }
});

// FG.createPointMarker = function (opts) {
//     // TODO: more tweaks
//     var lon, lat;
//     if (_.has(opts, 'lon') && _.has(opts, 'lat')) {
//         lon = opts.lon;
//         lat = opts.lat;
//     } else {
//         var info = opts.model.get('info');
//         lon = info.lon;
//         lat = info.lat;
//     }
//     var marker = new FG.PointMarker([lat, lon]);
//     return marker;
// };
//
// FG.createRectFeature = function (opts) {
//     // TODO: more tweaks
//     var lon1, lat1, lon2, lat2;
//     if (_.has(opts, 'lon1') && _.has(opts, 'lat1') && _.has(opts, 'lon2') && _.has(opts, 'lat2')) {
//         lon1 = opts.lon1;
//         lat1 = opts.lat1;
//         lon2 = opts.lon2;
//         lat2 = opts.lat2;
//     } else {
//         var info = opts.model.get('info');
//         lon1 = info.lon1;
//         lat1 = info.lat1;
//         lon2 = info.lon2;
//         lat2 = info.lat2;
//     }
//     var feature = new FG.RectFeature2([[lat1, lon1], [lat2, lon2]]);
//     //var feature = new FG.RectFigureLayer([[lat1, lon1], [lat2, lon2]]);
//     return feature;
// };

FG.createMultiLineFeature = function(opts) {
    var lonlats_list,
        path_opts;
    if (_.has(opts, 'lonlats_list')) {
        lonlats_list = opts.lonlats_list;
        path_opts = _.extend({}, opts.path_opts);
    } else {
        var info = opts.model.get('info');
        lonlats_list = info.lonlats_list;
        path_opts = _.extend({}, info.path_opts);
    }
    var latlngs = _.map(lonlats_list, function(lonlats) {
        return _.map(lonlats, function(lonlat) {
            return new L.LatLng(lonlat[1], lonlat[0])
        })
    });
    var feature = new FG.MultiLineFeature(latlngs, path_opts);
    return feature;
};

FG.createMultiPolygonFeature = function(opts) {
    var lonlats_list;
    if (_.has(opts, 'lonlats_list')) {
        lonlats_list = opts.lonlats_list;
    } else if (opts.model.has('hasMapData') && !!opts.model.get('hasMapData')) {
        lonlats_list = opts.model.mapData.get('geometry').coordinates;
    } else {
        return null;
    }
    // if ( opts.model.has('info') ){
    //    var info = opts.model.get('info');
    //    lonlats_list = info.lonlats_list;
    //}

    var n = 1,
        sub = lonlats_list[0];
    while (true) {
        if (_.isArray(sub)) {
            sub = sub[0];
            n += 1;
        } else {
            break;
        }
    }

    var latlngs;
    if (n == 2) {
        latlngs = _.map(lonlats_list, function(lonlat) {
            return new L.LatLng(lonlat[1], lonlat[0])
        });
        latlngs.pop();
    } else if (n == 3) {
        latlngs = _.map(lonlats_list, function(lonlats) {
            var onePoly = _.map(lonlats, function(lonlat) {
                return new L.LatLng(lonlat[1], lonlat[0])
            });
            onePoly.pop();
            return onePoly;
        });
    } else if (n == 4) {
        latlngs = [];
        _.each(lonlats_list, function(mpoly) {
            _.each(mpoly, function(lonlats) {
                var onePoly = _.map(lonlats, function(lonlat) {
                    return new L.LatLng(lonlat[1], lonlat[0])
                });
                onePoly.pop();
                latlngs.push(onePoly);
            })
        })
    }

    var feature = new FG.MultiPolygonFeature(latlngs, {
        color: '#fff',
        opacity: 0.9,
        weight: 2,
        fillColor: '#fff',
        fillOpacity: 0.2,
        imageOpacity: 0
    });
    return feature;
};

FG.RectFeature = L.Class.extend({
    includes: L.Mixin.Events,

    options: {
        opacity: 1,
        clickable: true
    },

    initialize: function (bounds, options) { // (String, LatLngBounds, Object)
        this._url = 'http://leafletjs.com/docs/images/logo.png';
        this._bounds = L.latLngBounds(bounds);
        this._latlng = this._bounds.getNorthEast();

        L.setOptions(this, options);
    },

    onAdd: function (map) {
        this._map = map;

        if (!this._image) {
            this._initImage();
            this._initEvents();
        }

        //map._panes.overlayPane.appendChild(this._image);
        map._panes.overlayPane.insertBefore(this._image, map._panes.overlayPane.firstChild);

        map.on('viewreset', this._reset, this);

        if (map.options.zoomAnimation && L.Browser.any3d) {
            map.on('zoomanim', this._animateZoom, this);
        }

        this._reset();
        this.fire('add');
    },

    onRemove: function (map) {
        map.getPanes().overlayPane.removeChild(this._image);

        map.off('viewreset', this._reset, this);

        if (map.options.zoomAnimation) {
            map.off('zoomanim', this._animateZoom, this);
        }
        this.fire('remove');
    },

    addTo: function (map) {
        map.addLayer(this);
        return this;
    },

    setOpacity: function (opacity) {
        this.options.opacity = opacity;
        this._updateOpacity();
        return this;
    },

    // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
    bringToFront: function () {
        if (this._image) {
            this._map._panes.overlayPane.appendChild(this._image);
        }
        return this;
    },

    bringToBack: function () {
        var pane = this._map._panes.overlayPane;
        if (this._image) {
            pane.insertBefore(this._image, pane.firstChild);
        }
        return this;
    },

    getAttribution: function () {
        return this.options.attribution;
    },

    _initImage: function () {
        this._image = L.DomUtil.create('div', 'leaflet-image-layer leaflet-clickable rect_figure_layer nodata');
        this.$image = $(this._image);
        this.$imageImg = $('<img />').appendTo(this.$image);
        this.imageUrl = '';

        if (this._map.options.zoomAnimation && L.Browser.any3d) {
            L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
        } else {
            L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
        }

        this._updateOpacity();
    },

    _animateZoom: function (e) {
        var map = this._map,
            image = this._image,
            scale = map.getZoomScale(e.zoom),
            nw = this._bounds.getNorthWest(),
            se = this._bounds.getSouthEast(),

            topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
            size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
            origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));

        image.style[L.DomUtil.TRANSFORM] =
            L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
    },

    _reset: function () {
        var image = this._image,
            topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
            size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);

        L.DomUtil.setPosition(image, topLeft);

        image.style.width = size.x + 'px';
        image.style.height = size.y + 'px';

        //this.$image.css('width', size.x + 'px');
        //this.$image.css('height', size.y + 'px');

        if (!!this.chart) {
            this.chart.setSize(size.x, size.y);
        }
    },

    _updateOpacity: function () {
        if (!!this._image) {
            L.DomUtil.setOpacity(this._image, this.options.opacity);
        }
    },

    updateFromModel: function (model) {
        var info = model.get('info');
        this.setBounds([
            [info.lat1, info.lon1],
            [info.lat2, info.lon2]
        ]);

    },

    loadImage: function(imageUrl) {
        var this_ = this;
        this.dropImage();
        this.imageUrl = imageUrl;
        if (!!this.$imageImg) {
            this.$imageImg.attr('src', '/data/'+imageUrl).on('load', function() {
                this_.$image.removeClass('nodata');
            });
        }
    },

    dropImage: function() {
        this.imageUrl = '';
        if (!! this.$imageImg ) {
            this.$imageImg.attr('src', '');
        }
        if (!! this.$image ) {
            this.$image.addClass('nodata');
        }
    },

    setBounds: function (bounds) {
        this._bounds = L.latLngBounds(bounds);
        this._latlng = this._bounds.getNorthEast();
        this._reset();
    },

    getBounds: function () {
        return this._bounds;
    },

    bindPopup: function (content, options) {

        if (content instanceof L.Popup) {
            this._popup = content;
        } else {
            if (!this._popup || options) {
                this._popup = new L.Popup(options, this);
            }
            this._popup.setContent(content);
        }

        if (!this._popupHandlersAdded) {
            this
                .on('click', this._openPopup, this)
                .on('remove', this.closePopup, this);

            this._popupHandlersAdded = true;
        }

        return this;
    },

    unbindPopup: function () {
        if (this._popup) {
            this._popup = null;
            this
                .off('click', this._openPopup)
                .off('remove', this.closePopup);

            this._popupHandlersAdded = false;
        }
        return this;
    },

    openPopup: function (latlng) {

        if (this._popup) {
            // open the popup from one of the path's points if not specified
            latlng = latlng || this._latlng ||
                this._latlngs[Math.floor(this._latlngs.length / 2)];

            this._openPopup({latlng: latlng});
        }

        return this;
    },

    closePopup: function () {
        if (this._popup) {
            this._popup._close();
        }
        return this;
    },

    _openPopup: function (e) {
        this._popup.setLatLng(e.latlng);
        this._map.openPopup(this._popup);
    },

    _initEvents: function () {
        if (this.options.clickable) {
            L.DomEvent.on(this._image, 'click', this._onMouseClick, this);

            //var events = ['dblclick', 'mousedown', 'mouseover',
            //    'mouseout', 'mousemove', 'contextmenu'];
            //for (var i = 0; i < events.length; i++) {
            //    L.DomEvent.on(this._image, events[i], this._fireMouseEvent, this);
            //}
        }
    },

    _onMouseClick: function (e) {
        if (this._map.dragging && this._map.dragging.moved()) { return; }
        this._fireMouseEvent(e);
    },

    _fireMouseEvent: function (e) {
        if (!this.hasEventListeners(e.type)) { return; }

        var map = this._map,
            containerPoint = map.mouseEventToContainerPoint(e),
            layerPoint = map.containerPointToLayerPoint(containerPoint),
            latlng = map.layerPointToLatLng(layerPoint);

        this.fire(e.type, {
            latlng: latlng,
            layerPoint: layerPoint,
            containerPoint: containerPoint,
            originalEvent: e
        });

        if (e.type === 'contextmenu') {
            L.DomEvent.preventDefault(e);
        }
        if (e.type !== 'mousemove') {
            L.DomEvent.stopPropagation(e);
        }
    },

    plotData: function (databundle, opts) {
        var this_ = this;
        opts = _.extend({vrange: 'fix'}, opts);
        databundle.getData().done(function(itemlist) {
            var data = [];
            var origData = itemlist[0].data;
            var suggest_range = origData.data.suggest_range;
            var cmapName = FG.getProperCmap(origData.data.suggest_cmap);
            var units = origData.data.units;
            var vrangeType = opts.vrange;
            var fit_range = origData.data.fit_range,
                fix_range = origData.data.fix_range,
                vranged = {fit: fit_range, fix: fix_range},
                vmin = vranged[vrangeType][0],
                vmax = vranged[vrangeType][1];

            var figUrlTemplate = _.template(origData.data.url_para);
            var figUrl = opts.uri + '?' + figUrlTemplate({
                format: 'figure',
                lat1: this_._bounds.getSouth(),
                lat2: this_._bounds.getNorth(),
                lon1: this_._bounds.getWest(),
                lon2: this_._bounds.getEast(),
                projection: 'merc',
                vmin: vmin,
                vmax: vmax,
                cmap: cmapName
            });
            DX.warn(figUrl);
            this_.loadImage(figUrl);
            //this_.$image.html('<img src="/data/'+figUrl+'" style="width:100%; height: 100%" />')
            if (!!opts) {
                if (!!opts.callback) {
                    opts.callback(itemlist);
                }
                if (!!opts.widget) {
                    opts.widget.setColorbar({
                        cmap: cmapName,
                        vmin: vmin,
                        vmax: vmax,
                        units: units
                    })
                }
            }
        });
        //this.$figure.html(DX.gen_uuid8());
    }

});


FG.MultiLineFeature = L.MultiPolyline.extend({
    statics: {
        className: 'LineFeature'
    },
    
    className: 'LineFeature',
    parentClass: L.MultiPolyline,
    
    options: {
        color: '#fff',
        opacity: 0.9,
        weight: 2,
        fillColor: '#fff',
        fillOpacity: 0.2,
        imageOpacity: 0
    },
    
    includes: [FG.RegionMixin, FG.FeatureTagMixin],
    
    initialize: function(latlngs, options) {
        this.parentClass.prototype.initialize.call(this, latlngs, options);
        this._bounds = this.getBounds();
        this._latlng = this._bounds.getNorthEast();
        if (_.has(options, 'tag')) {
            this.setTag(options.tag)
        }
    },
    
    updateFeature: function(latlngs, featureOpts) {
        if (_.has(featureOpts, 'tag')) {
            this.setTag(featureOpts.tag)
        }
        this.setLatLngs(latlngs);
        this._bounds = this.getBounds();
        this._latlng = this._bounds.getNorthEast();
        this.setStyle(featureOpts);
    },
    
    updateFromModel: function(model) {
        var info = model.get('info');
        var latlngs = _.map(info.lonlats_list, function(lonlats) {
            return _.map(lonlats, function(lonlat) {
                return new L.LatLng(lonlat[1], lonlat[0])
            })
        });
        var path_opts = _.extend({}, info.path_opts);
        this.setLatLngs(latlngs);
    },

    openPopup: function (latlng) {
        // open popup on the first layer
        for (var id in this._layers) {
            this.popupParent = this._layers[id].openPopup(latlng);
            break;
        }
        return this;
    },

    closePopup: function (latlng) {
        // open popup on the first layer
        if (!!this.popupParent) {
            this.popupParent.closePopup();
        }
        return this;
    }
});

